package com.gyp.service.impl;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.egzosn.pay.common.bean.PayOrder;
import com.gyp.config.WxPayConfig;
import com.gyp.config.idwork.IdContextHolder;
import com.gyp.constant.RedisKeyConstant;
import com.gyp.dto.pay.CreateOrderDTO;
import com.gyp.dto.pay.WxNotifyBussinDTO;
import com.gyp.dto.pay.WxNotifyDTO;
import com.gyp.entity.GyPayOrder;
import com.gyp.entity.GyPayPackage;
import com.gyp.entity.SysUserEntity;
import com.gyp.enums.OrderStatusEnum;
import com.gyp.enums.WxPayEnum;
import com.gyp.exception.BusinessException;
import com.gyp.mapper.user.GyPayOrderMapper;
import com.gyp.model.PageHandler;
import com.gyp.model.UserContextHolder;
import com.gyp.model.common.PageInfo;
import com.gyp.model.common.ResultCode;
import com.gyp.model.common.Search;
import com.gyp.service.GyPayOrderService;
import com.gyp.service.GyPayPackageService;
import com.gyp.service.UserService;
import com.gyp.utils.DateTimeUtil;
import com.gyp.utils.JSONUtils;
import com.gyp.utils.WxUtils;
import com.gyp.vo.pay.CreateOrderVO;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.util.PemUtil;
import com.wechat.pay.java.service.payments.nativepay.NativePayService;
import com.wechat.pay.java.service.payments.nativepay.model.Amount;
import com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest;
import com.wechat.pay.java.service.payments.nativepay.model.PrepayResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.time.LocalDateTime;
import java.util.Objects;

@Service
@Slf4j
@RequiredArgsConstructor
public class GyPayOrderImpl extends ServiceImpl<GyPayOrderMapper, GyPayOrder> implements GyPayOrderService {

    private final WxPayConfig wxPayConfig;

    private final GyPayPackageService payPackageService;

    private final UserService userService;

    private final RedissonClient redissonClient;

    @Override
    public CreateOrderVO wxPayCreate(CreateOrderDTO param) {
        GyPayPackage payPackage = payPackageService.getById(param.getPackageId());
        if (Objects.isNull(payPackage)) {
            throw new BusinessException("10046");
        }
        PrivateKey privateKey = PemUtil.loadPrivateKeyFromString(wxPayConfig.getPrivateKey());

        // 使用自动更新平台证书的RSA配置
        Config config = new RSAAutoCertificateConfig.Builder().merchantId(wxPayConfig.getMerchantId()) // 商户号
                .privateKey(privateKey)  // API证书地址（此处的路径自己调试一下，能找到就行）
                .merchantSerialNumber(wxPayConfig.getMerchantSerialNumber()) // API证书序列号
                .apiV3Key(wxPayConfig.getApiV3key()) // API密匙
                .build();
        Long orderId = IdContextHolder.nextId();
        save(GyPayOrder.builder()
                .id(orderId)
                .orderStatus(OrderStatusEnum.NOT_PAY.getCode())
                .packageId(param.getPackageId())
                .amount(payPackage.getAmount())
                .userId(UserContextHolder.getUserId())
                .build());


        // 构建service
        NativePayService service = new NativePayService.Builder().config(config).build();
        // request.setXxx(val)设置所需参数，具体参数可见Request定义
        PrepayRequest request = new PrepayRequest();
        Amount amount = new Amount();
        amount.setTotal(payPackage.getAmount().multiply(new BigDecimal("100")).intValue());
        request.setAmount(amount);
        request.setAppid(wxPayConfig.getAppId());
        request.setMchid(wxPayConfig.getMerchantId());
        request.setDescription("口型驱动充值订单");
        request.setNotifyUrl(wxPayConfig.getNotifyUrl());
        request.setOutTradeNo(String.valueOf(orderId));
        // 调用下单方法，得到应答
        PrepayResponse response = service.prepay(request);
        // 使用微信扫描 code_url 对应的二维码，即可体验Native支付
        CreateOrderVO createOrderVO = new CreateOrderVO();
        createOrderVO.setOrderId(String.valueOf(orderId));
        createOrderVO.setPayParam(response.getCodeUrl());
        return createOrderVO;
    }

    @Override
    public void wxNotifyHandler(WxNotifyDTO param) {
        WxNotifyBussinDTO notify = wxPayNotifyDecode(param);
        String tradeNo = notify.getOutTradeNo();
        String orderKey = String.format(RedisKeyConstant.PAY_ORDER_LOCK_KEY_PREFIX, tradeNo);
        RLock lock = redissonClient.getLock(orderKey);
        if (!lock.tryLock()) {
            log.info("[微信支付回调][当前已有回调正在处理]");
            return;
        }
        try {
            GyPayOrder order = lambdaQuery().eq(GyPayOrder::getId, Long.valueOf(tradeNo)).one();
            if (Objects.isNull(order) || !order.getOrderStatus().equals(OrderStatusEnum.NOT_PAY.getCode())) {
                throw new BusinessException("订单不存在或者订单状态不是未支付");
            }

            if (notify.getTradeState().equals(WxPayEnum.SUCCESS)) {
                handlerSuccessOrder(order, OrderStatusEnum.SUCCESS);
                return;
            } else if (WxPayEnum.CLOSED.equals(notify.getTradeState())) {
                //订单关闭
                handlerSuccessOrder(order, OrderStatusEnum.CLOSED);
                log.info("[微信支付失败][订单关闭]");
                return;
            } else if (WxPayEnum.PAYERROR.equals(notify.getTradeState())) {
                //订单充值失败
                handlerSuccessOrder(order, OrderStatusEnum.PAY_ERROR);
                log.info("[微信支付失败][订单失败]");
                return;
            }
            log.error("[微信支付][订单状态异常]:{}", notify);
        } catch (Exception e) {
            log.error("[微信支付回调异常][{}]", e);
        } finally {
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    @Override
    public Boolean checkOrder() {
        //检查是否有购买成功记录，购买过才可以查看教程等
        Long count = lambdaQuery()
                .eq(GyPayOrder::getUserId, UserContextHolder.getUserId())
                .eq(GyPayOrder::getOrderStatus, OrderStatusEnum.SUCCESS.getCode())
                .count();
        return count > 0;
    }

    @Override
    public GyPayOrder selectOrderDetail(Long orderId) {
        return getById(orderId);
    }

    @Override
    public PageInfo<GyPayOrder> selectOrders(Search search) {
        Page<GyPayOrder> page = lambdaQuery()
                .eq(GyPayOrder::getUserId, UserContextHolder.getUserId())
                .orderByDesc(GyPayOrder::getCreateTime)
                .page(new Page<>(search.getPageNum(), search.getPageSize()));
        return PageHandler.returnPage(page.getRecords(),search,page.getTotal());
    }

    /**
     * @Description 处理支付成功订单
     * @author xyb
     * @since 17:01 2024/1/16
     */
    private void handlerSuccessOrder(GyPayOrder order, OrderStatusEnum orderStatus) {
        //修改订单状态
        GyPayOrder update = new GyPayOrder();
        update.setOrderStatus(orderStatus.getCode());
        update.setId(order.getId());
        updateById(update);

        if (!OrderStatusEnum.SUCCESS.equals(orderStatus)) {
            //非支付成功变更，直接返回
            return;
        }

        Integer packageId = order.getPackageId();
        GyPayPackage payPackage = payPackageService.getById(packageId);
        //根据充值包增加对应的时长
        userService.lambdaUpdate()
                .eq(SysUserEntity::getId, order.getUserId())
                .setSql("voiceTime = voiceTime + " + payPackage.getDuration())
                .update();

        log.info("[支付成功][订单ID:{}][口型驱动时长变更:{}]", order.getId(), order.getId(),payPackage.getDuration());
    }

    public WxNotifyBussinDTO wxPayNotifyDecode(WxNotifyDTO param) {
        String key = wxPayConfig.getApiV3key();
        String associatedData = param.getResource().getAssociatedData();
        String ciphertext = param.getResource().getCiphertext();
        String nonce = param.getResource().getNonce();
        try {
            String decryptData = new WxUtils(key.getBytes(StandardCharsets.UTF_8)).decryptToString(associatedData.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), ciphertext);
            log.info("[微信支付回调][解密成功]:{}", decryptData);
            return JSONUtils.paraseJson(decryptData, WxNotifyBussinDTO.class);
        } catch (GeneralSecurityException e) {
            log.error("[微信支付回调][解密失败]:{}", e);
            throw new BusinessException(ResultCode.ERROR.getCode());
        } catch (IOException e) {
            log.error("[微信支付回调][解密失败]:{}", e);
            throw new BusinessException(ResultCode.ERROR.getCode());
        }

    }
}
